/**
 * Copyright (c) 2010 BullionXpress.com,
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package com.bullionxpress.server.impl;

import java.util.logging.Level;
import java.util.logging.Logger;
import com.bullionxpress.shared.model.OrderStatus;
import com.bullionxpress.shared.model.MarketOrder;
import com.bullionxpress.shared.model.LimitOrder;
import com.bullionxpress.shared.model.Order;
import com.bullionxpress.shared.service.DuplicateTransactionIDException;
import com.bullionxpress.shared.service.InvalidTransactionIDFormatException;
import com.bullionxpress.shared.service.MarketService;
import com.bullionxpress.server.application.ServerGlobals;
import com.bullionxpress.shared.model.Instrument;
import com.bullionxpress.shared.service.TransactionNotFoundException;
import com.bullionxpress.shared.util.Log;
import com.bullionxpress.shared.service.InsufficientFundingException;
import com.bullionxpress.shared.service.NoPriceAvailableException;
import com.bullionxpress.shared.service.OrderCannotCancelException;
import com.bullionxpress.shared.service.OrderNotFoundException;
import com.bullionxpress.shared.model.Position;
import com.bullionxpress.shared.model.Transaction;
import com.bullionxpress.shared.model.Account;
import com.bullionxpress.shared.model.TransactionType;
import com.bullionxpress.shared.service.AccountDatabase;
import com.bullionxpress.shared.service.OrderService;
import com.bullionxpress.shared.service.TransactionService;
import java.sql.Date;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

/**
 *
 * @author BullionXpress.com
 */
public class OrderManager extends java.rmi.server.UnicastRemoteObject implements MarketService, TransactionService, OrderService {

    
    public Queue getTransactions() {
        return ServerGlobals.transactions;
    }

    public Queue getTransactionListByAccount(String accountId) {
        try {
            //Iterator iterator = ServerGlobals.transactions.iterator();
            Queue q = new LinkedList();
            Transaction[] list = ServerGlobals.getTransactionManager().getTransactionHistoryByAccount(accountId);
            for (int i = 0; i < list.length; i++) {
                q.add(list[i]);
            }
            return q;
        } catch (TransactionNotFoundException ex) {
            //Log.info(ex);
        }
        return null;
    }

    public void clear() {
        int fc = 0;
        Iterator iterator = ServerGlobals.orders.iterator();
        while (iterator.hasNext()) {
            Object o = iterator.next();
            Order lo = (Order) o;
            if (lo.isFilled()) {
                lo.setStatus(OrderStatus.CLOSED);
                ServerGlobals.history.offer(lo);
                ServerGlobals.orders.remove(lo);
                fc++;
            }
            if (lo.isExpired()) {
                lo.setStatus(OrderStatus.EXPIRED);
                ServerGlobals.history.offer(lo);
                ServerGlobals.orders.remove(lo);
                fc++;
            }
        }
    }

    public void fill() {
        int fc = 0;
        Iterator iterator = ServerGlobals.orders.iterator();
        while (iterator.hasNext()) {
            Object o = iterator.next();
            if (o instanceof MarketOrder) {
                MarketOrder mo = (MarketOrder) o;
                if (mo.isOpen()) {
                    fill(mo);
                    fc++;
                }
            }
        }
        if (hasUnfilledMarketOrder()) {
            fill();
        }
    }

    public Order getOrderById(String id) throws OrderNotFoundException {
        Iterator iterator = ServerGlobals.orders.iterator();
        while (iterator.hasNext()) {
            Order o = (Order) iterator.next();
            if( o.getId() != null ){
                if (o.getId().equals(id)) {
                    return o;
                }
            }
        }
        throw new OrderNotFoundException();
    }

    public void fill(Order mo) {
        if (mo == null) {
            throw new IllegalArgumentException("Order cannot be null (fill)");
        }
        Order o = getMatch(mo);
        try {
            validate(mo, o);
        } catch (InsufficientFundingException ex) {
            Log.info("Order cannot be filled?");
            throw new IllegalStateException("Order cannot be filled?");
        }
        complete(mo, o);
        if (mo.getStatus() != OrderStatus.FILLED) {
            fill(mo);
        }
    }

    public void validate(Order a, Order b) throws InsufficientFundingException {
        if (a == null) {
            throw new IllegalArgumentException("Order cannot be null (validate)");
        }
        if (b == null) {
            throw new IllegalArgumentException("Order cannot be null (validate b)");            
        }
        if (a.getAccount() == null) {
            throw new IllegalArgumentException("Order Account cannot be null");
        }
        if (b.getAccount() == null) {
            throw new IllegalArgumentException("Order Account cannot be null");
        }
        if( !b.getInstrument().equals(b.getInstrument())){
            throw new IllegalArgumentException("These two orders cannot be matched");
        }
    }

    public void market(Order a, Order b) {
        if (a == null) {
            throw new IllegalArgumentException("Order a cannot be null");
        }
        if (b == null) {
            throw new IllegalArgumentException("Order b cannot be null");
        }
        try {
            if (a instanceof MarketOrder) {
                MarketOrder mo = (MarketOrder) a;
                if (mo.isSellOrder()) {
                    mo.setPrice(ServerGlobals.getQuoteManager().getBid(a.getInstrument()));
                } else if (mo.isBuyOrder()) {
                    mo.setPrice(ServerGlobals.getQuoteManager().getAsk(a.getInstrument()));
                }
            }
            if (b instanceof MarketOrder) {
                MarketOrder mo = (MarketOrder) b;
                if (mo.isSellOrder()) {
                    mo.setPrice(ServerGlobals.getQuoteManager().getBid(b.getInstrument()));
                } else if (mo.isBuyOrder()) {
                    mo.setPrice(ServerGlobals.getQuoteManager().getAsk(b.getInstrument()));
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    AccountDatabase db = new AccountRegistrar();
    public void complete(Order a, Order b) {
        if (a == null) {
            throw new IllegalArgumentException("Order a cannot be null");
        }
        if (b == null) {
            throw new IllegalArgumentException("Order b cannot be null");
        }
        market(a, b);
        long au = a.getRemainingUnits();
        long bu = b.getRemainingUnits();
        long qf = 0;
        if (au < bu) {
            bu = bu - au;
            qf = au;
            au = 0;
            a.setStatus(OrderStatus.FILLED);
        } else if (au > bu) {
            au = au - bu;
            qf = bu;
            bu = 0;
            b.setStatus(OrderStatus.FILLED);
        } else if (au == bu) {
            qf = au;
            au = 0;
            bu = 0;
            b.setStatus(OrderStatus.FILLED);
            a.setStatus(OrderStatus.FILLED);
        }
        try {
            a.addQuantityFilled(qf);
            b.addQuantityFilled(qf);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        try {
            Instrument instrument = a.getInstrument();
            if (instrument == null) {
                throw new IllegalStateException("Cannot open a position with a null instrument");
            }
            Position p = new Position();
            p.setUnits(qf);
            p.setInstrument(instrument);
            p.setAccount(a.getAccount());
            p.setEntryPrice(a.getPrice());
            open(p, a, b, qf);
            try {
                db.save(a.getAccount());
                db.save(b.getAccount());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            log(a, b, qf);
        } catch (InsufficientFundingException ex) {
            ex.printStackTrace();
        }
    }

    private void open(Position p, Order a, Order b, long qf) throws InsufficientFundingException {
        if (a == null) {
            throw new IllegalArgumentException("Order cannot be null (open position)");
        }
        if (b == null) {
            throw new IllegalArgumentException("Order cannot be null");
        }
        if (p == null) {
            throw new IllegalArgumentException("Position cannot be null");
        }
        if (a.isBuyOrder()) {
            Account aAcct = a.getAccount();
            Account bAcct = b.getAccount();
            double amount = a.getPrice() * qf;

            if (aAcct.getBalance() < amount) {
                throw new InsufficientFundingException();
            }

            Position op = new Position();
            op.setInstrument(p.getInstrument());
            op.setUnits(p.getUnits());
            op.setEntryPrice(p.getEntryPrice());
            op.setAccount(aAcct);

            Position cp = new Position();
            cp.setInstrument(p.getInstrument());
            cp.setUnits(p.getUnits());
            cp.setEntryPrice(p.getEntryPrice());
            cp.setAccount(bAcct);

            // VALIDATE BUY & SELL FIRST

            ServerGlobals.getPositionManager().open(op);
            ServerGlobals.getPositionManager().close(cp);

            aAcct.withdraw(amount);
            bAcct.deposit(amount);

        } else if (a.isSellOrder()) {
            // CLOSE POSITION FIRST?
            Account aAcct = a.getAccount();
            Account bAcct = b.getAccount();
            // HACK0R
            if (p.getInstrument() == null) {
                throw new IllegalStateException("Cannot close a position with a null instrument");
            }
            double amount = a.getPrice() * qf;
            if (bAcct.getBalance() < amount) {
                throw new InsufficientFundingException();
            }
            
            Position op = new Position();
            op.setInstrument(p.getInstrument());
            op.setUnits(p.getUnits());
            op.setEntryPrice(p.getEntryPrice());
            op.setAccount(bAcct);
        
            Position cp = new Position();
            cp.setInstrument(p.getInstrument());
            cp.setUnits(p.getUnits());
            cp.setEntryPrice(p.getEntryPrice());
            cp.setAccount(aAcct);
            
            // VALIDATE BUY & SELL FIRST
            ServerGlobals.getPositionManager().close(cp);
            ServerGlobals.getPositionManager().open(op);
            
            aAcct.deposit(amount);
            bAcct.withdraw(amount);


        }

        try {
            //db.connect();
            db.save(a.getAccount());
            db.save(b.getAccount());
        } catch (Exception ex) {
            throw new IllegalStateException("This is not supposed to occur:" + ex);
        }
    }
    //AccountRegistrar db = new AccountRegistrar();

    private void log(Order a, Order b, long units) {
        if (a == null) {
            throw new IllegalArgumentException("Order argument cannot be null");
        }
        if (b == null) {
            throw new IllegalArgumentException("Order argument cannot be null");
        }

        Transaction t = new Transaction();
        t.setPrice(a.getPrice());
        t.setInstrument(a.getInstrument());
        t.setTimestamp(System.currentTimeMillis());
        t.setType(TransactionType.TRADE);
        t.setDate(new Date(System.currentTimeMillis()));
        t.setUnits(units);
        a.setTransactionId(t.getId());
        b.setTransactionId(t.getId());
        if (a.isBuyOrder()) {
            t.setBuyerId(a.getAccount().getId());
            t.setSellerId(b.getAccount().getId());
        } else {
            t.setBuyerId(b.getAccount().getId());
            t.setSellerId(a.getAccount().getId());
        }
        if (ServerGlobals.verbose) {
            //System.out.println(t);
        }
        
        try {
            ServerGlobals.getTransactionManager().add(t);
        } catch (DuplicateTransactionIDException ex) {
            Logger.getLogger(OrderManager.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvalidTransactionIDFormatException ex) {
            Logger.getLogger(OrderManager.class.getName()).log(Level.SEVERE, null, ex);
        }
        ServerGlobals.transactions.offer(t);
    }

    public Order getMatch(Order o) {
        Order bp = null;
        if (o.isBuyOrder()) {
            bp = getMatchBuy(o);
        } else {
            bp = getMatchSell(o);
        }
        return bp;
    }

    public Order getMatchBuy(Order o) {
        Order bp = null;
        Iterator i = ServerGlobals.orders.iterator();
        while (i.hasNext()) {
            Object obj = i.next();
            if (obj == o) {
                continue;
            }
            // REFACTOR 
            if (obj instanceof LimitOrder) {
                LimitOrder lo = (LimitOrder) obj;
                if( !lo.getInstrument().equals(o.getInstrument())){
                    continue;
                }
                if (bp == null && lo.isOpen() && lo.isSellOrder()) {
                    bp = lo;
                }
                if (lo.isSellOrder() && lo.isOpen() && lo.getPrice() < bp.getPrice()) {
                    bp = lo;
                }
            }
            if (obj instanceof MarketOrder) {
                MarketOrder mo = (MarketOrder) obj;
                if( !mo.getInstrument().equals(o.getInstrument())){
                    continue;
                }
                if (bp == null && mo.isOpen() && mo.isSellOrder()) {
                    bp = mo;
                }
                if (mo.isSellOrder() && mo.isOpen()) { //&& mo.getPrice() < bp.getPrice()) {
                    bp = mo;
                    break; //return bp;
                }
            }
        }
        return bp;
    }

    public Order getMatchSell(Order o) {
        Order bp = null;
        Iterator i = ServerGlobals.orders.iterator();
        while (i.hasNext()) {
            Object obj = i.next();
            if (obj == o) {
                continue;
            }
            // REFACTOR
            if (obj instanceof LimitOrder) {
                LimitOrder lo = (LimitOrder) obj;
                if( !lo.getInstrument().equals(o.getInstrument())){
                    continue;
                }
                if (bp == null && lo.isOpen() && lo.isBuyOrder()) {
                    bp = lo;
                }
                if (lo.isBuyOrder() && lo.isOpen() && lo.getPrice() > bp.getPrice()) {
                    bp = lo;
                }
            }
            if (obj instanceof MarketOrder) {
                MarketOrder mo = (MarketOrder) obj;
                if( !mo.getInstrument().equals(o.getInstrument())){
                    continue;
                }
                if (bp == null && mo.isOpen() && mo.isBuyOrder()) {
                    bp = mo;
                }
                if (mo.isBuyOrder() && mo.isOpen()) { //&& mo.getPrice() > bp.getPrice()) {
                    bp = mo;
                    return bp;
                }
            }
        }
        return bp;
    }

    public Queue getOrderQueueByAccount(String accountId) {
        if (accountId == null) {
            throw new IllegalArgumentException("You cannot try to get orders from a null account");
        }
        Iterator iterator = ServerGlobals.orders.iterator();
        Queue q = new LinkedList();
        while (iterator.hasNext()) {
            Order o = (Order) iterator.next();
            Account acct = o.getAccount();
            if (acct == null) {
                throw new IllegalStateException("This should never occur");
            }
            String id = acct.getId();
            if (id == null) {
                throw new IllegalStateException("This should never occur");
            }
            if (id.equals(accountId)) {
                q.offer(o);
            }
        }
        return q;
    }

    public void offer(Order o) throws InsufficientFundingException, NoPriceAvailableException {
        if (o == null) {
            throw new IllegalArgumentException("Order cannot be null (offer)");
        }
        if (o.getAccount() == null) {
            throw new IllegalArgumentException("Cannot process an order with a null account");
        }
        if (o.getInstrument() == null) {
            throw new IllegalArgumentException("Cannot tender offer with null instrument");
        }
        if (o instanceof MarketOrder) {
            MarketOrder mo = (MarketOrder) o;
            if (mo.isBuyOrder()) {
                if (mo.getAccount().getBalance() < (o.getUnits() * o.getPrice())) {
                    throw new InsufficientFundingException();
                }
            }
            ServerGlobals.orders.offer(mo);
        } else if (o instanceof LimitOrder) {
            if (o.isBuyOrder()) {
                if (o.getAccount().getBalance() < (o.getUnits() * o.getPrice())) {
                    throw new InsufficientFundingException();
                }
            }
            ServerGlobals.orders.offer(o);
        }
    }

    public void cancel(Order o) throws OrderCannotCancelException {
        // if not filled.
        if (o.isOpen()) {
            ServerGlobals.orders.remove(o);
        } else {
            throw new OrderCannotCancelException();
        }
    }

    public OrderManager() throws RemoteException {
        //db.connect();
    }

    public boolean hasUnfilledOrder() {
        //throw new UnsupportedOperationException("Not supported yet.");
        Object[] q = ServerGlobals.orders.toArray();
        for (int i = 0; i < q.length; i++) {
            Order o = (Order) q[i];
            if (!o.isFilled()) {
                //System.out.println(o);
                return true;
            }
        }
        return false;
    }

    public boolean hasUnfilledMarketOrder() {
        //throw new UnsupportedOperationException("Not supported yet.");
        Object[] q = ServerGlobals.orders.toArray();
        for (int i = 0; i < q.length; i++) {
            Order o = (Order) q[i];
            if (o instanceof MarketOrder) {
                if (!o.isFilled()) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean hasUnfilledOrderByAccount(String id) {
        //throw new UnsupportedOperationException("Not supported yet.");
        Queue q = getOrderQueueByAccount(id);
        for (int i = 0; i < q.size(); i++) {
            Order o = (Order) q.remove();
            if (!o.isFilled()) {
                return true;
            }
        }
        return false;
    }

    public void connect() {
        //throw new UnsupportedOperationException("Not supported yet.");
    }

    
}
